/*  Copyright 2012 Bosch Mickaël - Brousset Mathias - Tellier Léo - Thomas Clément
 *
 *  This file is part of shared-drawing.
 * 
 *  shared-drawing is free software: you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free Software
 *  Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 *  shared-drawing is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License along with
 *  shared-drawing. If not, see http://www.gnu.org/licenses/.
 */

package server;

import SharedDrawing.Action;
import SharedDrawing.XmlGenerator;
import common.StreamToInput;
import common.XmlConstants;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

/** ClientThread
 *  This is the thread of a Client
 * 
 */
public class ClientThread extends Thread {
    
    private Socket sock;
    private InputStream iStream;
    private OutputStream oStream;
    
    private boolean is_alive;
    private boolean is_connected;
    private boolean try_to_join;
    private boolean up_to_date;
    private boolean has_leave;
    
    private String pass_word;
    private WorkGroupThread work_group;
    private XMLReader saxReader;
    private Integer user_id;
    private String wg_name;
    private Document buffer;
    private StreamToInput transformer;
    
    /**
     * Get the Client Identificator
     * @return An Interger of the Id
     */
    public Integer getUserId() {
        return user_id;
    }
    
    ClientThread(Socket newClient) {
        try {
            sock = newClient;
            iStream = sock.getInputStream();
            oStream = sock.getOutputStream();
            is_alive = true;
            is_connected = false;
            try_to_join = false;
            up_to_date = false;
            has_leave = false;
            buffer = new Document(new Element(XmlConstants.upToDate));
            transformer = new StreamToInput();
        } catch (IOException ex) {
            //Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
            System.err.println("[Client] You may need a network.");
            is_connected = true;
            
        }
    }

    /**
     * The main of the client thread
     */
    @Override
    public void run() {
        System.out.println("[Client] " + getName() + " is connected.");
        do {
        System.out.println("[Client] " + getName() + " has to join a workgroup.");
        //set up a connection parser
        try {
            saxReader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
        } catch (SAXException ex) {
            Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
        }
            
        saxReader.setContentHandler(new XmlReaderServerConnection(this));
        
        has_leave = false;
        //wait to connect
        boolean failed = false;
        while(!is_connected)
        {
            try {
                InputSource is = transformer.transform(iStream); 
                if(is == null)
                {
                    System.out.println("[Client] Nothing received : killing the client " + getName() + ".");
                    failed = true;
                    is_connected = true;
                }
                else
                {
                    
                    saxReader.parse(is);                
                }
            } catch (IOException ex) {
                Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
            } catch (SAXException ex) {
                Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
            }
            if(try_to_join)
            {
                user_id = Server.getServer().toWorkGroup(this, getName(), wg_name, pass_word);
                if(user_id == -1)
                {
                    System.out.println("[Client] " + getName() + " can't join " + wg_name + ".");
                    try_to_join = false;
                }
                else
                {
                    System.out.println("[Client] " + getName() + " has join " + wg_name + ".");
                    is_connected = true;
                }
            }
            else
            {
                System.out.println("[Client] " + getName() + " Not a join request.");
            }
        }
        if(!failed)
        {

            //connect the user
            Action action = new Action(XmlConstants.joinWorkgroup);
            action.addContent(XmlConstants.emitterNickName, getName());
            action.addContent(XmlConstants.emitterId, user_id.toString());
            Document doc = XmlGenerator.toXml(action);
            try {
                work_group.addToQueue(doc);
            } catch (InterruptedException ex) {
                Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
            }
            System.out.println("[Client] " + this.getName() + " is updating.");
            //get all data from another client
            if(!up_to_date)
            {
                work_group.askUpdate(this);
            }
            //now, the user is connected
            saxReader.setContentHandler(new XmlReaderServer(work_group, this, user_id.toString()));
            is_alive = true;
            System.out.println("[Client] " + this.getName() + "( " + this.getUserId() + " ) is ready.");
            while(is_alive)
            {
                InputSource source = transformer.transform(iStream);
                if(source == null)
                {
                    dieNow();
                }
                else
                {
                    try {
                        saxReader.parse(source);
                    } catch (IOException ex) {
                        Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (SAXException ex) {
                        Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
        
        } while (has_leave);
    }
    
    /**
     * Send a message to the remote Client
     * @param msg The data to send
     * @throws IOException  
     */
    synchronized public void send(String msg) throws IOException {
            OutputStreamWriter sending = new OutputStreamWriter(oStream);
            sending.write(msg);
            sending.flush();
    }
    
    /**
     * Send an action to the remote Client
     * @param action The action to send
     */
    synchronized public void send(Action action) {
        Document doc = XmlGenerator.toXml(action);
        XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
        String string = outputter.outputString(doc);
        System.out.println("sending action : " +string);
        try {
            send(string);
        } catch (IOException ex) {
            Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    /**
     * Set the connection information
     * @param nick_name The nickname of the remote Client
     * @param pass_word The password of the Workgroup
     * @param wg_name The name of the workgroup
     */
    public void connectMe(String nick_name, String pass_word, String wg_name)
    {
        try_to_join = true;
        this.setName(nick_name);
        this.pass_word = pass_word;
        this.wg_name = wg_name;
    }
    
    /**
     * Terminate the ClientThread
     */
    synchronized public void dieNow() {
        System.out.println("[Client] " + getName() + " is disconnected.");
        is_alive = false;
        try {
            iStream.close();
            Server.getServer().delClient(this);
            if (work_group != null)
                work_group.delClient(this);
        } catch (IOException ex) {
            Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Set the "up to date" status
     * @param up_to_date A boolean
     */
    public void setUpToDate(boolean up_to_date) {
        this.up_to_date = up_to_date;
    }
    
    /**
     * Get the "up to date" status
     * @return A boolean
     */
    public boolean getUpToDate(){
        return up_to_date;
    }

    /**
     * Change the associated WorkgroupThread
     * @param workgroup A reference to a WorkgroupThread
     */
    public void setWorkGroup(WorkGroupThread workgroup) {
        work_group = workgroup;
    }

    /**
     * Add a message to the buffer
     * @param msg The message to add
     */
    synchronized public void bufferize(Document msg) {
        buffer.getRootElement().addContent(msg.getRootElement().detach());
    }
    
    /**
     * Share the data to "up to date" other Clients
     */
    synchronized public void makeUpToDate(){
        up_to_date = true;
        XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
        String msg = outputter.outputString(buffer);
        try {
            System.out.println("sending to one : " + msg);
            send(msg);
        } catch (IOException ex) {
            Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Ask the updates
     * @param asker The id of the asker
     * @return A boolean
     */
    synchronized public boolean askUpdate(Integer asker) {
        if(!up_to_date)
        {
            return false;
        }
        Document doc = new Document(new Element(XmlConstants.askUpToDate));
        doc.getRootElement().setAttribute(XmlConstants.emitterId, asker.toString());
        XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
        String msg = outputter.outputString(doc);
        try {
            System.out.println("asking to one : " + msg);
            send(msg);
        } catch (IOException ex) {
            Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        return true;
    }
    
    /**
     * Leave the current Workgroup
     */
    public void leaveWorkGroup() {
        System.out.println("[Client] " + getName() + " leaves " + work_group.getName() + ".");
        up_to_date = false;
        is_alive = false;
        is_connected = false;
        work_group.delClient(this);
        work_group = null;
        has_leave = true;
        try_to_join = false;
    }
    
    
}
